#!/bin/bash

#----------------------------------------------------
# File: systemd.sh
# Contents: systemd架构
# Date: 18-11-9
#----------------------------------------------------

#---------------------------------------------------------------------------------------------------
# Systemd
#
# Systemd提供了比Upstart更激进的并行启动能力, 采用 socket/D-BS activation等技术启动服务. 显而易见的结果是更快的启动
# 速度.
# 为了减少启动时间, Systemd的目标是:
# - 尽可能启动更少的进程
# - 尽可能将更多进程并行启动
#
# 同样地, Upstart也试图实现这两个目标. Upstart采用事件驱动机制, 服务可以暂不启动, 当需要的时候通过事件触发其启动, 这符
# 合第一个设计目标; 此外不相干的服务可以并行启动, 这实现了第二个目标.
#
#
# Systemd提供按需启动能力
#
# 当Sysvinit系统初始化的时候, 它会将所有可能用到的后台服务进程全部启动运行. 并且系统必须等待所有的服务都启动就绪之后, 才允许
# 用户登录.
#
# 某些服务很可能在很长一段时间内, 整个服务器运行期间都没有被使用过. 比如CUPS, 打印服务在多数服务器上很少被真正使用到. 花费启
# 动这些服务上的时间是不必要的; 同样, 花费在这些服务上的系统资源也是一种浪费.
#
#
# Systemd采用Linux的Cgroup特性跟踪和管理进程的生命周期
#
# init系统的一个重要职责是负责跟踪和管理服务进程的生命周期. 它不仅可以启动一个服务, 它必须也能够停止服务.
#
# 服务进程一般都作为后台进程(daemon)在后台运行, 为此服务程序有时候会派生(fork)两次. 在Upstart当中, 需要在配置文件中正确地
# 配置expect节. 这样Upstart通过对fork系统调用进行计数, 从而获知真正的后台进程的PID.
#
# 还有更加特殊的情况. 比如吗一个CGI程序会派生两次, 从而脱离了和Apache的父子关系. 当Apache进程被停止后, 该CGI程序还在继续
# 运行. 而期望是Apache服务停止后, 所有由它启动的相关进程也被停止. 为了处理这类问题, Upstart通过strace来跟踪fork, exit
# 等系统调用, 但是这种方法很笨拙, 且缺乏可扩展性. Systemd利用了Linux内核的特性即Cgroup来完成跟踪的任务. 当停止服务时, 通
# 过查询Cgroup, Systemd可以准确找到所有的相关进程, 从而干净地停止服务.
#---------------------------------------------------------------------------------------------------

#---------------------------------------------------------------------------------------------------
# Systemd的基本概念
#
# 单元的概念
# 系统初始化需要做是事情非常多. 需要启动后台服务, 比如启动sshd服务; 需要做配置工作, 比如挂载文件系统. 这个过程中的每一步
# 都被Systemd抽象为一个配置单元, 即Unit. 可以认为一个服务是一个配置单元; 一个挂载点是一个配置单元; 一个交换分区的配置
# 是一个配置单元; 等等. Systemd将配置单元归纳为以下不同的类型.
#
# - service: 代表一个后台进程, 比如mysqld. 这是最常用的一类.
#
# - socket: 此类配置单元封装系统和互联网中的一个套接字. 当下, Systemd支持流式,数据报和连续包的AF_INET, AF_INET6,
# AF_UNIX socket. 每一个套接字配置单元都有一个相应的服务配置单元. 相应的服务在第一个"连接"进入套接字时就会启动. 例如,
# nscd.socket在有新的连接后便启动nscd.service.
#
# - device: 此类配置单元封装一个存在于Linux设备树中的设备. 每一个使用udev规则标记的设备都将会在Systemd中作为一个设备
# 配置单元出现.
#
# - mount: 此类配置单元封装文件系统结构层次中的一个挂载点. Systemd将对这个挂载点进行监控和管理. 比如可以在启动时自动将
# 其挂载; 可以在某些条件下自动卸载. Systemd会将/etc/fstab中的条目都转换为挂载点, 并在开机时处理.
#
# - automount: 此类配置单元封装系统结构层次中的一个自挂载点. 每一个自挂载配置单元对应一个挂载配置单元, 当该自动挂载点
# 被访问时, Systemd执行挂载点中定义的挂载行为.
#
# - swap: 和挂载配置单元类似, 交换配置单元用来管理交换分区. 用户可以用交换配置单元来定义系统中的交换分区, 可以让这些交
# 换分区在启动时被激活.
#
# - target: 此类配置单元为其他配置单元进行逻辑分组. 它们本身实际上并不做什么, 只是引用其他配置单元而已. 这样便可以对配
# 置单元做一个统一的控制. 这样就可以实现大家都已经非常熟悉的运行级别概念. 比如想让系统进入图形化模式, 需要运行许多服务和
# 配置命令, 这些操作由一个个的配置单元表示, 将所有这些配置单元组合为一个目标(target), 就表示需要将这些配置单元全部执行
# 一遍便进入target所代表的系统运行状态. 例如multi-user.target 相当于SysV系统中运行级别5
#
# - timer: 定时器配置单元用来定时触发用户定义的操作. 这类配置单元取代了atd, crond等传统的定时服务.
#
# - snapshot: 与target配置单元类似, 快照的一组配置单元. 它保存了系统当前的运行状态.
#
#
# 依赖关系
# 虽然Systemd将大量的启动解除了依赖, 使得它们可以并发启动. 但还是存在有些任务, 它们之间存在天生的依赖, 不能用"Socket
# activation(套接字激活)", D-BUS activation和autofs三大方法解除依赖. 比如: 挂载必须等待挂载点在文件系统中被创建;
# 挂载也必须等待相应的物理设备就绪. 为了解决这类依赖问题, Systemd的配置单元之间可以彼此定义依赖关系.
#
# Systemd用配置单元定义文件中的关键字来描述配置单元之间的依赖关系. 比如: Unit A 依赖Unit B, 可以在Unit B的定义中使
# 用"require A"来表示.
#
#
# Systemd的并发启动原理
#
# Systemd的开发人员仔细研究了服务之间的相互依赖的本质问题, 发现所谓依赖可以分为三个具体的类型, 而每一个类型实际上都可以
# 通过相应的u技术解除依赖关系.
#
# 并发启动原理之一: 解决socket依赖
#
# 绝大多数的服务依赖是套接字依赖. 比如服务A通过一个套接字端口S1提供自己的服务, 其他的服务如果需要服务A, 则需要连接S1.因
# 此如果服务A尚未启动, S1就不存在, 其他的服务就会得到启动错误, 所以传统地, 人们先启动服务A, 等待它进入就绪状态, 再启动
# 需要它的服务.
# Systemd认为, 只要我们预先把S1建立好, 那么其他所有的服务就可以同时启动而无需等待服务A来创建S1了. 如果服务A尚未启动,
# 那么其他进程向S1发送的服务请求实际上会被Linux操作系统缓存, 其他进程会在这个请求的地方等待. 一旦服务A启动就绪, 就可以
# 立即处理缓存的请求, 一切都开始正常运行.
#
# Linux操作系统有一个特性, 当进场调用fork或者exec创建子进程之后, 所有在父进程中被打开的文件句柄(file descriptor)都
# 被子进程所继承. 套接字也是一种文件句柄, 进程A]可以创建一个套接字, 以后当进程A调用exec启动一个新的子进程时, 只要确保该
# 套接字的 close_on_exec 标志位被清空, 那么新的子进程就可以继承这个套接字. 子进程看到的套接字和父进程看到的套接字是同
# 一个系统套接字, 就仿佛看到这个套接字是子进程自己创建的一样, 没有任何区别.
#
# 这个特性被一个叫做inetd的系统服务所利用, inetd进程会负责监控一些常用套接字端口, 比如telnet, 当该端口号有连接请求时,
# inetd才启动telnetd进程, 并把有连接的套接字传递给新的telnetd进程. inetd可以代理很多的网络服务, 这样可以节约很多的
# 系统负载和内存资源, 只有当有真正的连接请求时才启动相应服务, 并把套接字传递给相应的服务进程.
#
# 和inetd类似, Systemd是所有其他进程的父进程, 他可以先建立所有需要的套接字, 然后在调用exec的时候将该套接字传递给新的
# 服务进程, 而新进程直接使用该套接字进行服务即可.
#
#
# 并发启动原理之二: 解决D-BU依赖
#
# D-BUS是desktop-bus的简称. 是一个低延迟,低开销,高可用性的进程间通信机制. 它越来越多地使用于应用程序之间通信, 也用于
# 应用程序和操作系统内核之间的通信. 很多现代的服务进程都使用D-BUS取代套接字作为进程间通信机制, 对外提供服务. 比如简化Linux
# 网络配置的NetworkManager服务就使用D-BUS和其他的应用程序或者服务进行交互: 邮件客户端软件evolution可以通过D-BUS从
# NetworkManage服务获取网络状态的改变, 以便做出相应的处理.
#
# D-BUS支持所谓的"bus activation"功能. 如果服务A需要使用服务B的D-BUS服务, 而服务B并没有运行, 则D-BUS可以在服务A请
# 求服务B的D-BUS时自启动服务B. 而服务A发出的请求会被D-BUS缓存, 服务A会等待服务B启动就绪. 利用这个特性, 依赖D-BUS的服
# 务就可以实现并行启动.
#
#
# 并发启动原理之三: 解决文件系统依赖
#
# 系统启动过程中, 文件系统相关的活动是最耗时的, 比如挂载文件系统, 对文件系统进行磁盘检查(fsck), 磁盘配额检查等都是非常耗
# 时的操作. 在等待这些工作完成的同时, 系统处于空闲状态. 那些想使用文件系统的服务似乎必须等待文件系统初始化完成才可以启动.
# 但是Systemd发现这种依赖也是可以避免的.
#
# Systemd参考了autofs的设计思路, 使得依赖文件系统的服务和文件系统本身两者初始化可以并发工作. autofs可以检测到某个文件
# 系统挂载点真正被访问到的时候才触发挂载操作, 这是通过内核automounter模块的支持而实现的. 比如一个open()系统调用作用在
# "/misc/cd/file1"的时候, /misc/cd尚未执行挂载操作, 此时open()调用被挂起等待, Linux内核autofs执行挂载. 这个时候,
# 控制权返回给open()系统调用, 并正常打开文件.
#
# Systemd集成了autofs的实现, 对于系统中的挂载点, 比如/home, 当系统启动的时候, Systemd为其创建一个临时的自动挂载点.
# 在这个时刻/home真正的挂载设备尚未启动好, 真正的挂载操作还没有执行, 文件系统检测还没有完成. 可是那些依赖该目录的进程已
# 经可以并发启动, 它们的open()操作被内建在Systemd中的autofs捕获, 将该ope()调用挂起(可中断睡眠状态). 然后等待真正的
# 挂载操作完成, 文件系统检测完成后, Systemd将该自动挂载点替换为真正的挂载点, 并让open()调用返回. 由此, 实现了那些依赖
# 于文件系统的服务和文件系统本身同时并发启动.
#
# 当然对于"/"目录的依赖实际上一定还是串行执行, 因为Systemd自己也存放在"/"之下, 必须等待系统根目录挂载检查好.
#---------------------------------------------------------------------------------------------------

#===================================================================================================
# systemd架构:
#
#+---------------------------------------------------------------------------------------------------------------------+
#| systemd Utilities                                                                                                   |
#| +===========+ +===========+ +===========+ +===========+ +===========+ +===========+ +===========+ +===========+     |
#| | systemctl | |journalctl | |  notify   | |  analyze  | |   cgls    | |   cgtop   | |  loginctl | |   nspawn  |     |
#| +===========+ +===========+ +===========+ +===========+ +===========+ +===========+ +===========+ +===========+     |
#+---------------------------------------------------------------------------------------------------------------------+
#| +-------------------------+ +-------------------------------------------------------------------------------------+ |
#| | systemd Daemons         | | systemd Targets                                                                     | |
#| | +=======+               | | +========+ +======+ +--------------------+ +----------------+ +-------------------+ | |
#| | |systemd|               | | |bootmode| |basic | |     multi-user     | |    graphical   | |  user-session     | | |
#| | +=======+               | | +========+ +======+ | +====+ +=========+ | | +============+ | | +===============+ | | |
#| | +========+ +========+   | | +========+ +======+ | |dbus| |telephony| | | |user-session| | | |display service| | | |
#| | |journald| |networkd|   | | |shutdown| |reboot| | +====+ +=========+ | | +============+ | | +===============+ | | |
#| | +========+ +========+   | | +========+ +======+ | +====+ +=========+ | +----------------+ | +===============+ | | |
#| | +======+ +============+ | |                     | |dlog| |  logind | |                    | | tizen service | | | |
#| | |logind| |user session| | |                     | +====+ +=========+ |                    | +===============+ | | |
#| | +======+ +============+ | |                     +--------------------+                    +-------------------+ | |
#| +-------------------------+ +-------------------------------------------------------------------------------------+ |
#+---------------------------------------------------------------------------------------------------------------------+
#| systemd Core                                                                                                        |
#| +=======+ +--------------------------------------+ +-----------------------+ +=========+ +=========+                |
#| |manager| |             unit                     | |         login         | |namespace| |   log   |                |
#| +=======+ | +========+ +=====+ +======+ +======+ | | +=========+ +=======+ | +=========+ +=========+                |
#| +=======+ | |service | |timer| |mount | |target| | | |multiseat| |inhibit| | +=========+ +=========+                |
#| |systemd| | +========+ +=====+ +======+ +======+ | | +=========+ +=======+ | |  cgroup | |   dbus  |                |
#| +=======+ | +========+ +=====+ +======+ +======+ | | +=========+ +=======+ | +=========+ +=========+                |
#|           | |snapshot| |path | |socket| | swap | | | | session | |  pam  | |                                        |
#|           | +========+ +=====+ +======+ +======+ | | +=========+ +=======+ |                                        |
#|           +--------------------------------------+ +-----------------------+                                        |
#+---------------------------------------------------------------------------------------------------------------------+
#| systemd Libraries                                                                                                   |
#| +=============+ +=============+ +=============+ +=============+ +=============+ +=============+ +=============+     |
#| |   dbus-1    | |    libpam   | |   libcap    | |libcryptsetup| | tcpwrapper  | |  libaudit   | |  libnotify  |     |
#| +=============+ +=============+ +=============+ +=============+ +=============+ +=============+ +=============+     |
#+---------------------------------------------------------------------------------------------------------------------+
#|                               +================+    +================+    +================+                        |
#| Linux Kernel                  |    cgroups     |    |     autofs     |    |     kdbus      |                        |
#|                               +================+    +================+    +================+                        |
#+---------------------------------------------------------------------------------------------------------------------+
#
# systemctl是systemd的主命令, 用于管理系统.
# sudo systemctl reboot
# sudo systemctl poweroff 关闭系统
# sudo systemctl halt    cpu停止工作
# sudo systemctl suspend  暂停系统
# sudo systemctl hybrid-sleep 交互式休眠
#
#
# systemd-analyze 查看启动耗时
# systemd-analyze blame 查看每个服务的启动耗时
# systemd-analyze critical-chain 查看启动过程流
#
#
# hostnamectl 查看当前主机的信息
# hostnamectl set-hostname xx 设置主机名称
#
#
# localectl 查看本地化设置
#
#
# timedatectl 查看当前时区设置
#
#
# loginctl 查看当前登录的用户
# loginctl list-seeions 列出当前的Session
# loginctl list-users 列出当前登录的用户
#
#===================================================================================================
#
# Unit 资源单位
# 常用的资源:
#   service 服务
#   target 多个Unit构成的一个组, 运行级别
#   device 设备
#   mount 挂载
#   slice 进程组
#   swap swap文件
#   socket 进程通信的socket
#
# systemctl list-units [OPTIONS] 查询系统的Unit
#
# Unit状态:
# systemctl status [NAME] // NAME可以是具体的资源, 比如 user.slice
# systemctl is-active NAME // 某个Unit是否正在运行
# systemctl is-failed NAME // 某个Unit是否处于启动失败状态
# systemctl is-enabled NAME // 某个Unit是否建立了启动链接
#
# 管理Unit:
# sudo systemctl start|stop|kill|reload UNIT
# sudo systemctl daemon-reload 重新加载所有修改过的配置文件
#
# sudo systemctl show UNIT  显示Unit所有底层参数
# sudo systemctl show -p PARAM UNIT 显示Unit的具体参数
# sudo systemctl set-property UNIT PARAM=VALUE 设置属性
#
# 依赖关系:
# systemctl list-dependcies [UNIT]
#
#===================================================================================================
#
# Unit的配置文件
# systemd默认从目录/etc/systemd/system/读取配置文件. 但是里面大部分文件都是符号链接, 指向目录/usr/lib/systemd/system/
#
# systemctl enable UNIT 用于在上面的两个目录之间,建立符号链接关系
#
#
# 配置文件状态:
# systemctl list-unit-files [--type=TYPE]
#   enabled: 已经建立启动链接
#   disabled: 没有建立启动链接
#   static: 该配置文件没有[Install]部分(无法执行), 只能作为其他配置文件的依赖
#   masked: 该配置文件被禁止建立启动链接
#
#
# 配置文件格式:
# systemctl cat UNIT  查看UNIT配置文件的内容
#
# [Unit]
#   Requires: 当前Unit依赖其他Unit, 强依赖
#   Wants: 弱依赖
#   Before: 如果该字段指定的Unit也要启动,那么必须在当前Unit之后启动
#   After: 如果该字段指定的Unit也要启动,那么必须在当前Unit之前启动
#   Condition: 当前Unit运行满足的条件
# [Install]
#   WantedBy: 一个或多个Target, 当前Unit激活时符号链接会放入/etc/systemd/system目录下以Target名+.wants后缀构成的子目录
#   RequiredBy: 强依赖
# [Service]
#   Type: 启动时进程的行为. simple(默认值),执行ExecStart指定的命令,启动主进程. forking, 以fork的方式从父进程创建子进程,创建后
#         父进程会立即退出. oneshot,一次性进程,systemd会等当前服务退出,再继续往下执行. dbus, 当前服务通过D-Bus启动. notify,当
#         前服务启动玩吧,会通知systemd,再继续往下执行. idle, 若其他任务执行完毕, 当前服务才会执行.
#   ExecStart: 启动当前服务的命令
#   ExecStartPre: 启动当前服务之前执行的命令
#   ExecStartPost: 启动当前服务之后执行的命令
#   ExecReload: 重启服务时执行的命令
#   ExecStop: 停止当前服务时执行的命令
#   RestartSec: 自动重启当前服务间隔的秒数
#   Restart: 定义何种情况下systemd会自动重启当前服务. always, on-success, on-failure, on-abnormal, on-abort, on-watchdog
#   TimeoutSec: 定义systemd停止当前服务之前等待的秒数
#   Environment: 指定环境变量
#
#===================================================================================================
#
# 日志管理:
# journalctl 查看所有的日志(内核日志和应用日志)
#
# sudo journalctl -k 查看内核日子
# sudo journalctl -b 查看系统本次启动的日志
#
# sudo journalctl _PID=PID 查看指定进程的日志
# sudo journalctl SCRIPT 查看指定脚本的日志
# sudo journalctl -u UNIT 查看UNIT的日志
#
# sudo journalctl -n NUM  尾部最新NUM行日志
#
# sudo journalctl -f  实时显示最新日志
#
#===================================================================================================
